Linux系统TTY串口驱动实例详解

您所在的位置:网站首页 linux rs232驱动 Linux系统TTY串口驱动实例详解

Linux系统TTY串口驱动实例详解

2024-07-09 21:41| 来源: 网络整理| 查看: 265

目录 一、简介二、源码详解2.1 uart_driver的注册:2.2 上层tty_core层2.3 注册platform_driver驱动 三、其他相关链接Linux下tty串口驱动数据的发送、接收过程源码实例详解

一、简介

在Linux系统中,终端是一类字符型设备,它包括多种类型,通常使用tty来简称各种类型的终端设备。由于串口也是一种终端,因此这里引入终端这个概念 。 Linux tty子系统包含:tty核心,tty线路规程和tty驱动。tty核心是对整个tty设备的抽象,对用户提供统一的接口,tty线路规程是对传输数据的格式化,tty驱动则是面向tty设备的硬件驱动。它们的关系如下图: 在这里插入图片描述 简单来分的话可以说成两层,一层是下层我们的串口驱动层,它直接与硬件相接触,我们需要填充一个 struct uart_ops 的结构体,另一层是上层 tty 层,包括 tty 核心以及线路规程,它们各自都有一个 ops 结构,用户空通过间是 tty 注册的字符设备节点来访问,这么说来如上图所示涉及到了4个 ops 结构了,层层跳转。下面,就来分析分析它们的层次结构。

二、源码详解

从整个串口驱动注册程序来看,该程序只做了两件事: 1、注册uart_driver; 2、注册platform_driver,也就是uart_add_one_port(); 具体请细看下面的源码分析:

2.1 uart_driver的注册:

在 s3c2440 平台,它是这样来注册串口驱动的,分配一个struct uart_driver 简单填充,并调用uart_register_driver 注册到内核中去。

static struct uart_driver s3c24xx_uart_drv = { .owner = THIS_MODULE, .dev_name = "s3c2410_serial", .nr = CONFIG_SERIAL_SAMSUNG_UARTS, .cons = S3C24XX_SERIAL_CONSOLE, .driver_name = S3C24XX_SERIAL_NAME, .major = S3C24XX_SERIAL_MAJOR, .minor = S3C24XX_SERIAL_MINOR, }; static int __init s3c24xx_serial_modinit(void) { int ret; ret = uart_register_driver(&s3c24xx_uart_drv); if (ret uart_port ,这个uart_port 是需要我们从其它地方调用 uart_add_one_port 来添加的。

下面先分析串口驱动层的uart_state:

struct uart_state { struct tty_port port; int pm_state; struct circ_buf xmit; struct tasklet_struct tlet; struct uart_port *uart_port; // 对应于一个串口设备 };

分配空间uart_driver->uart_state[nr]空间,即申请 nr 个 uart_state 空间,用来存放驱动所支持的串口(端口)的物理信息;

struct uart_port { spinlock_t lock; /* port lock */ unsigned long iobase; /* io端口基地址(物理) */ unsigned char __iomem *membase; /* io内存基地址(虚拟) */ unsigned int (*serial_in)(struct uart_port *, int); void (*serial_out)(struct uart_port *, int, int); unsigned int irq; /* 中断号 */ unsigned long irqflags; /* 中断标志 */ unsigned int uartclk; /* 串口时钟 */ unsigned int fifosize; /* 串口缓冲区大小 */ unsigned char x_char; /* xon/xoff char */ unsigned char regshift; /* 寄存器位移 */ unsigned char iotype; /* IO访问方式 */ unsigned char unused1; unsigned int read_status_mask; /* 关心 Rx error status */ unsigned int ignore_status_mask; /* 忽略 Rx error status */ struct uart_state *state; /* pointer to parent state */ struct uart_icount icount; /* 串口信息计数器 */ struct console *cons; /* struct console, if any */ #if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(SUPPORT_SYSRQ) unsigned long sysrq; /* sysrq timeout */ #endif upf_t flags; unsigned int mctrl; /* 当前的Moden 设置 */ unsigned int timeout; /* character-based timeout */ unsigned int type; /* 端口类型 */ const struct uart_ops *ops; /* 串口端口操作函数 */ unsigned int custom_divisor; unsigned int line; /* 端口索引 */ resource_size_t mapbase; /* io内存物理基地址 */ struct device *dev; /* 父设备 */ unsigned char hub6; /* this should be in the 8250 driver */ unsigned char suspended; unsigned char unused[2]; void *private_data; /* generic platform data pointer */ };

这个结构体,是需要我们自己来填充的,比如我们 s3c2440 有3个串口,那么就需要填充3个 uart_port ,并且通过 uart_add_one_port 添加uart_driver->uart_state->uart_port 中去。当然 uart_driver 有多个 uart_state ,每个 uart_state 有一个 uart_port 。在 uart_port 里还有一个非常重要的成员 struct uart_ops *ops ,这个也是需要我们自己来实现的,后面代码会详细分析从用户层到硬件层是如何一步步通过ops进行调用的。

struct uart_ops { unsigned int (*tx_empty)(struct uart_port *); /* 串口的Tx FIFO缓存是否为空 */ void (*set_mctrl)(struct uart_port *, unsigned int mctrl); /* 设置串口modem控制 */ unsigned int (*get_mctrl)(struct uart_port *); /* 获取串口modem控制 */ void (*stop_tx)(struct uart_port *); /* 禁止串口发送数据 */ void (*start_tx)(struct uart_port *); /* 使能串口发送数据 */ void (*send_xchar)(struct uart_port *, char ch); /* 发送xChar */ void (*stop_rx)(struct uart_port *); /* 禁止串口接收数据 */ void (*enable_ms)(struct uart_port *); /* 使能modem的状态信号 */ void (*break_ctl)(struct uart_port *, int ctl); /* 设置break信号 */ int (*startup)(struct uart_port *); /* 启动串口,应用程序打开串口设备文件时,该函数会被调用 */ void (*shutdown)(struct uart_port *);/* 关闭串口,应用程序关闭串口设备文件时,该函数会被调用 */ void (*flush_buffer)(struct uart_port *); void (*set_termios)(struct uart_port *, struct ktermios *new, struct ktermios *old); /* 设置串口参数 */ void (*set_ldisc)(struct uart_port *);/* 设置线路规程 */ void (*pm)(struct uart_port *, unsigned int state, unsigned int oldstate); /* 串口电源管理 */ int (*set_wake)(struct uart_port *, unsigned int state); /* * Return a string describing the type of the port */ const char *(*type)(struct uart_port *); /* * Release IO and memory resources used by the port. * This includes iounmap if necessary. */ void (*release_port)(struct uart_port *); /* * Request IO and memory resources used by the port. * This includes iomapping the port if necessary. */ int (*request_port)(struct uart_port *); /* 申请必要的IO端口/IO内存资源,必要时还可以重新映射串口端口 */ void (*config_port)(struct uart_port *, int); /* 执行串口所需的自动配置 */ int (*verify_port)(struct uart_port *, struct serial_struct *); /* 核实新串口的信息 */ int (*ioctl)(struct uart_port *, unsigned int, unsigned long); #ifdef CONFIG_CONSOLE_POLL void (*poll_put_char)(struct uart_port *, unsigned char); int (*poll_get_char)(struct uart_port *); #endif }; 2.2 上层tty_core层

tty 层要从 register_uart_driver 来看起了,因为 tty_driver 是在注册过程中构建的,我们也就顺便了解了注册过程。

int uart_register_driver(struct uart_driver *drv) { struct tty_driver *normal = NULL; int i, retval; /* 根据driver支持的最大设备数,申请n个 uart_state 空间,每一个 uart_state 都有一个uart_port */ drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL); /* tty层:分配一个 tty_driver ,并将drv->tty_driver 指向它 */ normal = alloc_tty_driver(drv->nr); drv->tty_driver = normal; /* 对 tty_driver 进行设置 */ normal->owner = drv->owner; normal->driver_name = drv->driver_name; normal->name = drv->dev_name; normal->major = drv->major; normal->minor_start = drv->minor; normal->type = TTY_DRIVER_TYPE_SERIAL; normal->subtype = SERIAL_TYPE_NORMAL; normal->init_termios = tty_std_termios; normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600; normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; normal->driver_state = drv; tty_set_operations(normal, &uart_ops); /* * Initialise the UART state(s). */ for (i = 0; i nr; i++) { struct uart_state *state = drv->state + i; struct tty_port *port = &state->port; /* driver->state->tty_port */ tty_port_init(port); port->close_delay = 500; /* .5 seconds */ port->closing_wait = 30000; /* 30 seconds */ /* 初始化 tasklet */ tasklet_init(&state->tlet, uart_tasklet_action, (unsigned long)state); } /* tty层:注册 driver->tty_driver */ retval = tty_register_driver(normal); }

uart_register_driver(&s3c24xx_uart_drv)注册驱动时会有下面几个操作: 1、分配空间uart_driver->uart_state[nr]空间,即申请 nr 个 uart_state 空间,用来存放驱动所支持的串口(端口)的物理信息; 2、分配tty_driver,将dev_name/major/minor/nr赋值给tty_driver; 3、设置ty_core层的ops为strucy tty_operations uart_ops,设置flags为TTY_DRIVER_REAL_RAM|TTY_DRIVER_DYNAMIC_DEV; 4、初始化tty_port:uart_driver->uart_state[nr]->tty_port,如ttybuffer、flush_to_ldisc、tty_port->ops=&uart_port_ops(struct uart_port_ops uart_port_ops); 5、注册tty_driver,后面从源码中可以分析出uart驱动的注册,实际就是tty_driver的注册,都是将uart的参数传给tty_driver,然后注册字符设备、分配设备文件、将驱动写进总线管理的tty_driver链表中; 注:从代码可以看到uart_register_drive()函数中最终要调用tty_register_driver()函数来注册tty驱动,因此与用户空间打交道的工作完全交给了 tty_driver ,而且这一部分都是内核实现好的,我们不需要修改,了解一下工作原理即可;

static const struct tty_operations uart_ops = { .open = uart_open, .close = uart_close, .write = uart_write, .put_char = uart_put_char, // 单字节写函数 .flush_chars = uart_flush_chars, // 刷新数据到硬件函数 .write_room = uart_write_room, // 指示多少缓冲空闲的函数 .chars_in_buffer= uart_chars_in_buffer, // 只是多少缓冲满的函数 .flush_buffer = uart_flush_buffer, // 刷新数据到硬件 .ioctl = uart_ioctl, .throttle = uart_throttle, .unthrottle = uart_unthrottle, .send_xchar = uart_send_xchar, .set_termios = uart_set_termios, // 当termios设置被改变时又tty核心调用 .set_ldisc = uart_set_ldisc, // 设置线路规程函数 .stop = uart_stop, .start = uart_start, .hangup = uart_hangup, // 挂起函数,当驱动挂起tty设备时调用 .break_ctl = uart_break_ctl, // 线路中断控制函数 .wait_until_sent= uart_wait_until_sent, #ifdef CONFIG_PROC_FS .proc_fops = &uart_proc_fops, #endif .tiocmget = uart_tiocmget, // 获得当前tty的线路规程的设置 .tiocmset = uart_tiocmset, // 设置当前tty线路规程的设置 #ifdef CONFIG_CONSOLE_POLL .poll_init = uart_poll_init, .poll_get_char = uart_poll_get_char, .poll_put_char = uart_poll_put_char, #endif };

这个是 tty 核心的 ops ,简单一看,后面分析调用关系时,我们在来看具体的里边的函数,下面来看 tty_driver 的注册。

int tty_register_driver(struct tty_driver *driver) { int error; int i; dev_t dev; void **p = NULL; if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM) && driver->num) { p = kzalloc(driver->num * 2 * sizeof(void *), GFP_KERNEL); } /* 如果没有主设备号则申请 */ if (!driver->major) { error = alloc_chrdev_region(&dev, driver->minor_start, driver->num, driver->name); } else { dev = MKDEV(driver->major, driver->minor_start); error = register_chrdev_region(dev, driver->num, driver->name); } if (p) { /* 为线路规程和termios分配空间 */ driver->ttys = (struct tty_struct **)p; driver->termios = (struct ktermios **)(p + driver->num); } else { driver->ttys = NULL; driver->termios = NULL; } /* 创建字符设备,使用 tty_fops */ cdev_init(&driver->cdev, &tty_fops); driver->cdev.owner = driver->owner; error = cdev_add(&driver->cdev, dev, driver->num); mutex_lock(&tty_mutex); /* 将该 driver->tty_drivers 添加到全局链表 tty_drivers */ list_add(&driver->tty_drivers, &tty_drivers); mutex_unlock(&tty_mutex); if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) { for (i = 0; i num; i++) tty_register_device(driver, i, NULL); } /* 向proc文件系统注册driver */ proc_tty_register_driver(driver); driver->flags |= TTY_DRIVER_INSTALLED; return 0; }

从上面源码可以分析到: 1、为线路规程和termios分配空间,并使 tty_driver 相应的成员指向它们。 2、注册字符设备,名字是 uart_driver->name 我们这里是“ttySAC”,文件操作函数集是 tty_fops。 3、将该 uart_driver->tty_drivers 添加到全局链表 tty_drivers 。 4、向 proc 文件系统添加 driver。

2.3 注册platform_driver驱动

该函数就是实现将驱动挂到platform总线上去,总线接管设备和驱动的管理工作。

int s3c24xx_serial_init(struct platform_driver *drv, struct s3c24xx_uart_info *info) { dbg("s3c24xx_serial_init(%p,%p)\n", drv, info); #ifdef CONFIG_PM drv->suspend = s3c24xx_serial_suspend; drv->resume = s3c24xx_serial_resume; #endif return platform_driver_register(drv); } static void __exit s3c2440_serial_exit(void) { platform_driver_unregister(&s3c2440_serial_driver); } module_init(s3c2440_serial_init); module_exit(s3c2440_serial_exit);

然后我们分析一下platform_driver函数:

static struct platform_driver s3c2440_serial_driver = { .probe = s3c2440_serial_probe, .remove = __devexit_p(s3c24xx_serial_remove), .driver = { .name = "s3c2440-uart", .owner = THIS_MODULE, }, };

在平台设备注册驱动的时候,总线会对驱动和设备匹配,如果匹配成功,将调用驱动的prob函数,我们具体追一下s3c2440_serial_driver 的prob函数:

int s3c24xx_serial_probe(struct platform_device *dev, struct s3c24xx_uart_info *info) { struct s3c24xx_uart_port *ourport; int ret; dbg("s3c24xx_serial_probe(%p, %p) %d\n", dev, info, probe_index); ourport = &s3c24xx_serial_ports[probe_index]; probe_index++; dbg("%s: initialising port %p...\n", __func__, ourport); ret = s3c24xx_serial_init_port(ourport, info, dev); if (ret port); platform_set_drvdata(dev, &ourport->port); ret = device_create_file(&dev->dev, &dev_attr_clock_source); if (ret port; struct s3c2410_uartcfg *cfg; struct resource *res; int ret; dbg("s3c24xx_serial_init_port: port=%p, platdev=%p\n", port, platdev); if (platdev == NULL) return -ENODEV; cfg = s3c24xx_dev_to_cfg(&platdev->dev); if (port->mapbase != 0) return 0; if (cfg->hwport > CONFIG_SERIAL_SAMSUNG_UARTS) { printk(KERN_ERR "%s: port %d bigger than %d\n", __func__, cfg->hwport, CONFIG_SERIAL_SAMSUNG_UARTS); return -ERANGE; } /* setup info for port */ port->dev = &platdev->dev; ourport->info = info; /* copy the info in from provided structure */ ourport->port.fifosize = info->fifosize; dbg("s3c24xx_serial_init_port: %p (hw %d)...\n", port, cfg->hwport); port->uartclk = 1; if (cfg->uart_flags & UPF_CONS_FLOW) { dbg("s3c24xx_serial_init_port: enabling flow control\n"); port->flags |= UPF_CONS_FLOW; } /* sort our the physical and virtual addresses for each UART */ res = platform_get_resource(platdev, IORESOURCE_MEM, 0);// 取得物理地址 if (res == NULL) { printk(KERN_ERR "failed to find memory resource for uart\n"); return -EINVAL; } dbg("resource %p (%lx..%lx)\n", res, res->start, res->end); port->mapbase = res->start; port->membase = S3C_VA_UART + res->start - (S3C_PA_UART & 0xfff00000); // 静态映射 ret = platform_get_irq(platdev, 0); if (ret irq = 0; else { port->irq = ret; ourport->rx_irq = ret; ourport->tx_irq = ret + 1; } ret = platform_get_irq(platdev, 1); if (ret > 0) ourport->tx_irq = ret; ourport->clk = clk_get(&platdev->dev, "uart"); dbg("port: map=%08x, mem=%08x, irq=%d (%d,%d), clock=%ld\n", port->mapbase, port->membase, port->irq, ourport->rx_irq, ourport->tx_irq, port->uartclk); /* reset the fifos (and setup the uart) */ s3c24xx_serial_resetport(port, cfg); return 0; }

上面代码主要完成3项工作: 1、取串口的基地址 2、取串口的中断号 3、复位FIFO 在回到s3c24xx_serial_probe函数,在初始化串口后,接下来完成下面的操作: 1、添加端口uart_add_one_port 2、添加属性文件,这样在sys下面就可以看到串口的信息了 3、初始化动态频率调节s3c24xx_serial_cpufreq_register。

至此uart_driver和platform_driver都已注册完毕,后续章节会细讲TTY驱动数据的收发过程。

三、其他相关链接 Linux下tty串口驱动数据的发送、接收过程源码实例详解


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3